/*
 * miniFreeRTOS SOC System emulation.
 *
 * Copyright (c) 2024- yanl1229@163.com.
 * Written by yanl1229
 *
 * This code is licensed under the GPL.
 */

#include "irq.h"
#include "task.h"

uint32_t interrupt_nest_count = 0xffffffffUL;
/**
 * 进入临界区
 */
void disable_interrupt(void)
{
    /* 屏蔽中断(高于系统调用的优先级中断才能被响应) */
    irq_mask();
    interrupt_nest_count++;
}

/**
 * 退出临界区
 */
void enable_interrupt(void)
{
    interrupt_nest_count--;

    if (interrupt_nest_count == 0)
        irq_unmask();
}


#ifdef __GNUC__
/**
 * @brief  This function handles SVCall exception.
 * @param  None
 * @retval None
 */
void SVC_Handler(void)
{
    __asm volatile(
        "	mov	r3, %0		                    \n" /* 获取当前任务的地址 */
        "	ldr r1, [r3]					    \n" /* 获得当前任务内容 */
        "	ldr r0, [r1]					    \n" /* 获的当前任务stack的地址 */
        "	ldmia r0!, {r4-r11, r14}		    \n" /* 将当前任务stack上寄存器中的内容恢复到CPU寄存器(软件操作) */
        "	msr psp, r0						    \n" /* 设置Thread运行模式下的sp */
        "	isb								    \n"
        "	mov r0, #0 						    \n"
        "	msr	basepri, r0					    \n" /* 将0赋值给中断屏蔽寄存器，为0时开启所有中断 */
        "	bx r14								\n" /* bx 执行后，自动恢复R0~R3 R12 LR PC 和PSP的值,  */
        "										\n" /* 任务从上次发生中断的地方地方的下一条指令开始执行(PC指针) */
        ::"r"(&current) : "memory");
}

/**
 * @brief  This function handles PendSVC exception.
 * @param  None
 * @retval None
 */

void PendSV_Handler(void)
{
    __asm volatile(
        "	mrs r0, psp							\n" /* 发生异常时current任务的psp栈指针 */
        "	isb									\n"
        "										\n"
        "	mov	r3, %0			                \n" /* current地址保存在r3中 */
        "	ldr	r2, [r3]						\n" /* 当前正在执行任务的task_struct保存在r2中 */
        "										\n"
        "	stmdb r0!, {r4-r11, r14}			\n" /* 将r4~r11 14寄存器中值，压入current的栈中 */
        "	str r0, [r2]						\n" /* 保存current指针到psp栈顶 */
        "										\n"
        "	stmdb sp!, {r0, r3}					\n" /* 将R0，R3临时压入到hander模式的msp主栈 */
        "	mov r0, %1 							\n" /* r0 = configMAX_SYSCALL_INTERRUPT_PRIORITY */
        "	msr basepri, r0						\n" /* 将r0写入中断屏蔽寄存器，关闭5~15中断优先级的中断 */
        "	dsb									\n"
        "	isb									\n"
        "	bl switch_context				    \n" /* 查找下一个要运行的任务 */
        "	mov r0, #0							\n"
        "	msr basepri, r0						\n" /* 将0写入中断屏蔽寄存器，开启所有中断 */

        "	ldmia sp!, {r0, r3}					\n" /* 恢复之前在hander模式的msp主栈上保存的r0和r3 */
        "										\n"
        "	ldr r1, [r3]						\n" /* 取出r3地址中的值，其实就是新赋值的current */
        "	ldr r0, [r1]						\n" /* 获取r3地址中的内容，其实就是新任务的栈顶的内容保存在r0中 */
        "										\n"
        "	ldmia r0!, {r4-r11, r14}			\n" /* 将r0地址中的值出栈，恢复到r4~r11,r14,即设置新任务的cpu寄存器的值 */
        "										\n"
        "	msr psp, r0							\n" /* 将r0的值赋值给psp, 设置新任务在Thread运行模式下的sp */
        "	isb									\n"
        "										\n"
        "	bx r14								\n" /* bx 执行后，自动恢复R0~R3 R12 LR PC 和PSP的值,  */
        "										\n" /* 任务从上次发生中断的地方地方的下一条指令开始执行(PC指针) */
        "										\n" ::"r"(&current),
        "i"(CONFIG_MAX_SYSCALL_INTERRUPT_PRIORITY) : "memory");
}
#else
__asm void SVC_Handler(void)
{
    extern current;

    /* Get the location of the current TCB. */
    ldr r3, =current
    ldr r1, [r3]
    ldr r0, [r1]
    /* Pop the core registers. */
    ldmia r0!, {r4-r11,r14}
    msr psp, r0
    isb
    mov r0, #0
    msr basepri, r0
    bx r14
}

__asm void PendSV_Handler(void)
{
    extern switch_context;
    extern current;

    mrs r0, psp
    isb

    ldr r3, =current
    ldr r2, [r3]

    /* Save the core registers. */
    stmdb r0!, {r4-r11, r14}

    /* Save the new top of stack into the first member of the task_struct. */
    str r0, [r2]

    stmdb sp!, {r0, r3}
    mov r0, #CONFIG_MAX_SYSCALL_INTERRUPT_PRIORITY
    msr basepri, r0
    dsb
    isb
    bl switch_context
    mov r0, #0
    msr basepri, r0
    ldmia sp!, {r0, r3}

    /* The first item in current is the task top of stack. */
    ldr r1, [r3]
    ldr r0, [r1]

    /* Pop the core registers. */
    ldmia r0!, {r4-r11, r14}

    msr psp, r0
    isb

    bx r14

}
#endif
/**
 * @brief  This function handles SysTick Handler.
 * @param  None
 * @retval None
 */
void SysTick_Handler(void)
{
    if (get_sched_stat() == SCHED_RUNNIING)
        sched_tick();
}
